package crystal.workshop.airss;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import mujl.io.FileExtensionFilter;

import crystal.lib.io.ResReader;

import crystal.workshop.StructureSearcher;
import crystal.workshop.StructureSource;
import crystal.workshop.io.DiskReader;
import crystal.workshop.ui.SimpleStructureList;
import crystal.workshop.ui.StructureDataModel;
import crystal.workshop.ui.StructuresListModel;


public class Airss implements StructureSource, StructureSearcher {
	
	/**
	 * Define the regular expressions for extracting information about the structure.
	 */
	protected static final Pattern PATTERN_SPACE_GROUP = Pattern.compile("\\((.*)\\)");
	protected static final Pattern PATTERN_TIMES_FOUND = Pattern.compile("n - (\\d*)");
	
	/** The directory where the search is stored. */
	private final File searchDirectory;

	private final DiskReader reader;
	
	protected final SimpleStructureList strList = new SimpleStructureList(); 
	
	/**
	 * Constructs an AIRSS instance from an existing search stored in a local
	 * folder.
	 * @param searchDirectory the search directory.
	 */
	public Airss(final File searchDirectory ) {
		this(searchDirectory, null);
	}

	/**
	 * Constructs an AIRSS instance from an existing search stored in a local
	 * folder.  The user can optionally pass in a file reader manager to use
	 * for loading structure data from the disk. 
	 * 
	 * @param searchDirectory the search directory.
	 * @param readerManager the reader manager to use for loading structures.
	 * Can be null in which case an internal reader manager will be used.
	 * @throws IllegalArgumentException if the search directory passed is null
	 * or not a directory.
	 */
	public Airss(final File searchDirectory, final DiskReader diskReader)
	throws IllegalArgumentException {
		if(searchDirectory == null || !searchDirectory.isDirectory()) {
			throw new IllegalArgumentException("The selected directory is not valid.");
		}
		
		if(diskReader == null) {
			reader = new DiskReader();
		} else {
			this.reader = diskReader;
		}
		this.searchDirectory = searchDirectory;
		
		initialise();
	}
	
	/**
	 * Called by the constructor to set up this <code>Airss</code> instance.
	 */
	private void initialise() {
		// Check if the reader manager already has a '.res' reader.
		// otherwise add one
		if(!reader.getFileReaderManager().containsReaderOfType(ResReader.class)) {
			reader.getFileReaderManager().addReader(new ResReader());
		}
		
		updateAll();
	}
	
	@Override
	public void updateAll() {
		// Get all the .res files in the search directory	
		syncWith(searchDirectory.listFiles(new FileExtensionFilter( "res" )));
	}

	@Override
	public boolean update(StructureDataModel struct) {
		final boolean updated = reader.update(struct);
		if(updated)
			parseStructureTitle(struct);
		return updated;
	}

	@Override
	public int getNumberOfStructures() {
		return strList.getSize();
	}

	@Override
	public Collection<StructureDataModel> getStructures() {
		return strList.getStructures();
	}
	
	@Override
	public StructuresListModel getStructuresList() {
		return null;
	}

	@Override
	public String getName() {
		return "AIRSS";
	}
	
	/**
	 * This method does three things:<br/>
	 * 1) Loads any new structure in <code>strFiles</code>
	 * 2) Removes any structures that aren't in <code>strFiles</code>
	 * 3) Updates all structures that are not up to date
	 * @param strFiles the list of structure files to synchronise with.
	 */
	public void syncWith(final File[] strFiles) {
		
		// Get all the .res files in the search directory
		final LinkedList<File> syncFiles = new LinkedList<File>(Arrays.asList(strFiles));
		
		for(StructureDataModel str : strList.getStructures()) {
			// Remove it from the list if we already have it
			syncFiles.remove(reader.getStructureFile(str));
			update(str);	// Update it
		}
 
		// Add new ones
		File currentFile = null;
		StructureDataModel newStr = null;
		for( final Iterator<File> iter = syncFiles.iterator(); iter.hasNext();) {
			currentFile = iter.next();
			newStr = reader.loadStructure(currentFile);
			if(newStr != null) {
				strList.add(newStr, reader);
				parseStructureTitle(newStr);
			}
		}
	}

	/**
	 * Parse the title from a structure being loaded.  The res files being
	 * should come have a title with the following format:
	 * [NAME] [PRESSURE] [VOLUME] [ENTHALPY] ([SPACE_GROUP]) n - [NO. OF TIMES FOUND]
	 * @param str
	 */
	private void parseStructureTitle(final StructureDataModel str) {
		final String origName = str.getCustomName();
		final StringTokenizer st = new StringTokenizer(origName);
		
		String value = st.nextToken();
		str.setCustomName(value);
		
		if(!st.hasMoreTokens()) return;
		value = st.nextToken();
		try {
			final Double pressure = Double.valueOf(value);
			str.addProperty("Pressure", pressure);
		} catch(NumberFormatException e) {
			System.err.println("Error parsing pressure: " + value );
		}
			
		if(!st.hasMoreTokens()) return;
		value = st.nextToken();
		str.addProperty("Volume", value);
		try {
			final Double volume = Double.valueOf(value);
			str.addProperty("Volume", volume);
		} catch(NumberFormatException e) {
			System.err.println("Error parsing volume: " + value );
		}
		
		if(!st.hasMoreTokens()) return;
		value = st.nextToken();
		try {
			final Double enthalpy = Double.valueOf(value);
			str.addProperty("Enthalpy", enthalpy);
		} catch(NumberFormatException e) {
			System.err.println("Error parsing enthalpy: " + value );
		}
		
		if(!st.hasMoreTokens()) return;
		value = st.nextToken();
		int remainder = origName.indexOf(value);
		
		// Try and find the space group in the remainder
		final Matcher sg = PATTERN_SPACE_GROUP.matcher(origName.substring(remainder, origName.length()));
		if(sg.find())
		{
			value = null;
			try {
				value = sg.group(1);
			} catch(final IllegalStateException e) {
				System.err.println( "The previous match operation failed" ); 
			} catch( final IndexOutOfBoundsException e) {
				System.err.println("There is no capturing group in the pattern with the given index");
			}
			
			if(value != null && !value.isEmpty())
				str.addProperty("Space group", value);
			
			// Move the remainder on
			remainder += sg.end(); 
		}
		
		// Now try and find the number of times this structure has been found
		final Matcher timesFound = PATTERN_TIMES_FOUND.matcher(origName.substring(remainder, origName.length()));
		if(timesFound.find())
		{
			value = null;
			try {
				value = timesFound.group(1);
			} catch(final IllegalStateException e) {
				System.err.println( "The previous match operation failed" ); 
			} catch( final IndexOutOfBoundsException e) {
				System.err.println("There is no capturing group in the pattern with the given index");
			}
			
			if(value != null && !value.isEmpty()) {
				try {
					final Integer intTimesFound = Integer.valueOf(value);
					str.addProperty("No. times found", intTimesFound);
				} catch (NumberFormatException e) {
					System.err.println("Warning: parsing title for " + str.getCustomName() +
							" number of times found format wrong, expected integer but got: " + value);
				}
			}
				
			// Move the remainder on
			remainder += timesFound.end();
		}
	}
}
